﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace Sworm
{
    public class SqlServerDialect : SqlDialect
    {
        public SqlServerDialect()
        {
            Sql_SelectInsertedRowId = "select scope_identity();";

            Map(typeof(DateTime), DbType.DateTime2, "datetime2");
        }
    }

    public class SqliteDialect : SqlDialect { }

    public class PostgresDialect : SqlDialect
    {
        public PostgresDialect()
        {
            Sql_SelectInsertedRowId = "select lastval();";

            Map(typeof(Boolean), DbType.Boolean, "boolean");
            Map(typeof(Guid), DbType.Guid, "uuid");
            Map(typeof(DateTime), DbType.DateTime, "timestamp without time zone");
            Map(typeof(Byte[]), DbType.Binary, "bytea");
        }
    }

    public class MySqlDialect : SqlDialect
    {
        public MySqlDialect()
        {
            Sql_SelectInsertedRowId = "select last_insert_id();";

            Map(typeof(Guid), DbType.Guid, "char(36)");
        }

        protected override string EscapeIdentifier(string identifier)
        {
            // backticks always work, quotes only work if ANSI_QUOTES is on
            return "`" + identifier + "`";
        }
    }

    public class SqlDialect
    {
        public SqlDialect()
        {
            Sql_SelectInsertedRowId = "select last_insert_rowid();";

            SetupDictionaries();
        }

        public virtual string EscapeTableName(string tableName)
        {
            return EscapeIdentifier(tableName);
        }

        public virtual string EscapeColumnName(string columnName)
        {
            return EscapeIdentifier(columnName);
        }

        public virtual string EscapeAlias(string alias)
        {
            return EscapeIdentifier(alias);
        }

        protected virtual string EscapeIdentifier(string identifier)
        {
            return "\"" + identifier + "\"";
        }

        public virtual SqlIdentifier CreateColumnAlias(string table, string column)
        {
            string name = string.Format("_{0}.{1}", table, column);
            return new SqlIdentifier(name, EscapeColumnName(name));
        }

        protected readonly Dictionary<Type, string> sqlTypeMap
            = new Dictionary<Type, string>();

        protected readonly Dictionary<Type, DbType> dbTypeMap
            = new Dictionary<Type, DbType>();

        protected readonly Dictionary<DbType, string> sqlTypeNameMap
            = new Dictionary<DbType, string>();

        public virtual DbType GetDbTypeFor(Type clrType)
        {
            return dbTypeMap[clrType];
        }

        public virtual string GetSqlTypeFor(Type clrType)
        {
            return sqlTypeMap[clrType];
        }

        public string GetSqlTypeName(DbType sqlType)
        {
            return sqlTypeNameMap[sqlType];
        }

        public bool IsColumnType(Type clrType)
        {
            return dbTypeMap.ContainsKey(clrType);
        }

        public string Sql_SelectInsertedRowId { get; protected set; }

        private void SetupDictionaries()
        {
            Map(typeof(Byte[]), DbType.Binary, "varbinary(8000)");
            Map(typeof(Single), DbType.Single, "float");
            Map(typeof(Boolean), DbType.Boolean, "bit");
            Map(typeof(Byte), DbType.Byte, "integer");
            Map(typeof(Char), DbType.StringFixedLength, "char(1)");
            Map(typeof(Guid), DbType.Guid, "uniqueidentifier");
            Map(typeof(Int16), DbType.Int16, "integer");
            Map(typeof(Int32), DbType.Int32, "integer");
            Map(typeof(Int64), DbType.Int64, "bigint");
            Map(typeof(Double), DbType.Double, "float");
            Map(typeof(String), DbType.String, "varchar(4000)");
            Map(typeof(Decimal), DbType.Decimal, "decimal(18,8)");
            Map(typeof(DateTime), DbType.DateTime, "datetime");
        }

        protected void Map(Type clrType, DbType dbType, string sqlType)
        {
            dbTypeMap[clrType] = dbType;
            sqlTypeMap[clrType] = sqlType;
            sqlTypeNameMap[dbType] = sqlType;

            if (clrType.IsValueType && Nullable.GetUnderlyingType(clrType) == null)
            {
                var nullableType = typeof(Nullable<>).MakeGenericType(clrType);
                dbTypeMap[nullableType] = dbType;
                sqlTypeMap[nullableType] = sqlType;
            }
        }
    }
}
